package org.sagitta.httprpc;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.sagitta.httprpc.exception.RPCAppException;
import org.sagitta.httprpc.exception.RPCException;
import org.sagitta.httprpc.message.ExceptionResponse;
import org.sagitta.httprpc.message.FaultResponse;
import org.sagitta.httprpc.message.Request;
import org.sagitta.httprpc.message.Response;
import org.sagitta.httprpc.utils.CommonUtil;

public class HttpRPCServlet extends javax.servlet.http.HttpServlet implements javax.servlet.Servlet {
	static final long serialVersionUID = 1L;
	private static final Log log = LogFactory.getLog(HttpRPCServlet.class);
	private WebController webController;
	private RequestUnmarshaller unmarshaller;
	private ResponseMashaller marshaller;
	private String getForward;
	private ApplicationContext applicationContext = ApplicationContext.getInstance();
	private ServiceConfig config;

	private static final String PATHSEPARATOR = "/";

	public HttpRPCServlet() {
		super();
		webController = WebController.newInstance();
		config = new ServiceConfig();
	}

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		if (CommonUtil.isNullString(getForward)) {

		} else {
			this.getServletConfig().getServletContext().getRequestDispatcher(getForward).forward(request, response);
		}
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String path = request.getPathInfo();
		response.setContentType("xml/text");
		response.setCharacterEncoding("utf-8");
		try {
			applicationContext.setHttpRequest(request);
			applicationContext.setSession(request.getSession());
			if (log.isDebugEnabled()) {
				log.debug("URL: " + path);
			}
			Response rpcResponse = getRPCResponse(path, request.getInputStream());
			OutputStream stream = response.getOutputStream();
			marshaller.marshal(rpcResponse, stream);
			stream.flush();
		} catch (Exception e) {
			e.printStackTrace();
			response.sendError(500);
		}
	}

	@SuppressWarnings("unchecked")
	private Response getRPCResponse(String path, InputStream in) {
		if (path == null || PATHSEPARATOR.equals(path)) {
			if (log.isDebugEnabled()) {
				log.debug("Can not match the action for the path:" + path);
			}
			return FaultResponse.createActionNotExistResponse();
		} else {
			String[] ss = path.split(PATHSEPARATOR);
			if (ss.length > 3) {
				if (log.isDebugEnabled()) {
					log.debug("Can not match the action for the path:" + path);
				}
				return FaultResponse.createActionNotExistResponse();
			} else {
				String controllerName = ss[1];
				String actionName = ss.length > 2 ? ss[2] : null;
				try {
					WebAction webAction = webController.getWebAction(controllerName, actionName);
					if (webAction == null) {
						if (log.isDebugEnabled()) {
							log.debug("Can not match the action for the path:" + path);
						}
						return FaultResponse.createActionNotExistResponse();
					}
					Request request = (Request) unmarshaller.unmashal(webAction.getRequestClassType(), in);
					if (request != null) {
						request.validate();
					}
					Response response = webAction.invoke(request);
					return response;
				} catch (InvocationTargetException ie) {
					Throwable t = peelInvocationTargetException(ie);
					if (t instanceof RPCAppException) {
						// It is an application level error, such as password
						// not match etc.
						RPCAppException pwe = (RPCAppException) t;
						return new ExceptionResponse(pwe.getCode(), pwe.getDetail());
					}
					if (t instanceof RPCException) {
						// It is a system level error, such as action not exist,
						// hibernate sql error etc.
						// POXException pe = (POXException) t;
						return new FaultResponse();
					} else {
						// Unknown exception, includes all other exception
						return new FaultResponse();
					}
				} catch (RPCAppException pwe) {
					return new ExceptionResponse(pwe.getCode(), pwe.getDetail());
				} catch (RPCException pe) {
					return new FaultResponse();
				} catch (Exception e) {
					return new FaultResponse();
				}
			}
		}
	}

	private Throwable peelInvocationTargetException(InvocationTargetException ie) {
		Throwable t = ie.getCause();
		while (t instanceof InvocationTargetException) {
			ie = (InvocationTargetException) t;
			t = ie.getCause();
		}
		return t;
	}

	public void init() throws ServletException {
		super.init();
		try {
			config.initializeConfiguriation(this.getServletContext().getRealPath("/") + "WEB-INF");
			getForward = config.getGetForward();
			initializeMessageAdaptor(config.getFactoryClass(), config.getAdaptorParameters());
			webController.initializeControllerPackage(config.getControllerPackage());
		} catch (Exception e) {
			e.printStackTrace();
			throw new ServletException();
		}
	}

	@SuppressWarnings("unchecked")
	private void initializeMessageAdaptor(String factoryClass, Map<String, String> parameters) throws Exception {
		Class clazz = Class.forName(factoryClass);
		Object obj = clazz.newInstance();
		if (obj instanceof AdaptorFactory) {
			AdaptorFactory factory = (AdaptorFactory) obj;
			Set<Map.Entry<String, String>> entires = parameters.entrySet();
			Iterator<Map.Entry<String, String>> it = entires.iterator();
			while (it.hasNext()) {
				Map.Entry<String, String> entry = it.next();
				String name = entry.getKey();
				String value = entry.getValue();
				try {
					Field f = clazz.getDeclaredField(name);
					f.setAccessible(true);
					f.set(obj, value);
					f.setAccessible(false);
				} catch (Exception e) {
					log.info("Can not found field " + name + " in the class " + clazz.getCanonicalName());
				}
			}
			factory.initializeFactory();
			this.marshaller = factory.createMashaller();
			this.unmarshaller = factory.createUnmashaller();
		} else {
			log.error("The " + factoryClass + " is not a validate Adaptor Factory Class");
		}
	}
}